
using namespace System;
using namespace System::Globalization;
using namespace System::Text;
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Collections::Generic;
using namespace System::Threading;
using namespace System::Windows;
using namespace System::Windows::Input;
using namespace System::Windows::Controls;
using namespace System::Windows::Media::Imaging;
using namespace System::Windows::Media::Animation;
using namespace System::Windows::Media;
using namespace System::Runtime::InteropServices;
using namespace System::Text::RegularExpressions;

#include "search.h"
#include "math.h"


namespace pdfviewnet
{	


	public ref class ImageControl : UserControl, INotifyPropertyChanged
	{
		PdfDocument* document;
		Object^ docLock;
		Canvas^ controlCanvas;

		int currentpage;
		int destPage;
		int targetPage;

		List<BookMark^>^ bookmarks;					
		List<int>^ history;

		static Windows::Style^ bookmarkStyle;
		static Pen^ blackPen = gcnew Pen(Brushes::Black, 1);

		System::Windows::Controls::ToolTip^ tip;
		VectorAnimation^ offsetanimation;		
		DoubleAnimation^ zoomanimation;

		BackgroundWorker^ renderWorker;
		SearchWorker^ searchWorker;

		cli::array<BitmapSource^>^ bitmapCache;
		bool updatepending;
		Point dragEnd;	
		Point dragStart;
		String^ currentSelection;
		bool zoomed;

		EventHandler^ delOnLayoutUpdated;
	public:

		PdfDocument* GetDocument() { return document; } 
		Object^ GetDocLock() { return docLock; } 

		String^ GetCurrentSelection()
		{ return currentSelection; }

		void StopAnimation(DependencyProperty^ dependencyproperty)
		{
			Object^ current = GetValue(dependencyproperty);
			BeginAnimation(dependencyproperty, nullptr);					
			SetValue(dependencyproperty, current);
		}

		ImageControl()
		{
			docLock = gcnew Object();				
			this->VisualBitmapScalingMode = BitmapScalingMode::LowQuality;
			this->Content = controlCanvas = gcnew Canvas();
			this->Focusable = true;			
			this->FocusVisualStyle = nullptr;
			this->DataContext = this;
			dragEnd = Point(Double::NaN, Double::NaN);
			dragStart = Point(Double::NaN, Double::NaN);
			targetPage = -1;
			this->history = gcnew List<int>();
			tip = gcnew System::Windows::Controls::ToolTip();

			delOnLayoutUpdated =  gcnew EventHandler(this, &ImageControl::OnLayoutUpdated);
			LayoutUpdated += delOnLayoutUpdated;
			
			System::Windows::Data::Binding^ binding2 = gcnew System::Windows::Data::Binding("ZoomPanTransform");
			binding2->Source = this;
			SetBinding(RenderTransformProperty, binding2);

			bookmarkStyle = gcnew Windows::Style();	
			bookmarkStyle->TargetType = TextBox::typeid;

			bookmarkStyle->Setters->Add(gcnew Setter(TextBox::CursorProperty, Cursors::Hand));			
			bookmarkStyle->Setters->Add(gcnew Setter(TextBox::BorderThicknessProperty, Thickness(1)));			
			bookmarkStyle->Setters->Add(gcnew Setter(TextBox::PaddingProperty, Thickness(3)));			
			bookmarkStyle->Setters->Add(gcnew Setter(TextBox::FocusableProperty, false));			
			bookmarkStyle->Setters->Add(gcnew Setter(TextBox::BackgroundProperty, Brushes::LightYellow));
			controlCanvas->Resources->Add("bookmark", bookmarkStyle);
		}

		
	


		// searching
		void CancelSearch()
		{
			if (searchWorker == nullptr)
				return;
			
			if (searchWorker->IsBusy)
			{
				searchWorker->CancelAsync();				
			}

			searchWorker = nullptr;
		}

		void Search(String^ text)
		{			
			CancelSearch();

			if (String::IsNullOrEmpty(text))
			{
				bookmarks = nullptr;
				UpdateBookmarks();
				return;
			}

			ZoomTo(document->ComputeDefaultZoom(currentpage, ActualWidth, ActualHeight));

			if (searchWorker == nullptr)
			{
				searchWorker = gcnew SearchWorker(this);
				searchWorker->ProgressChanged += gcnew ProgressChangedEventHandler(this, &ImageControl::OnSearchProgress);
			}

			searchWorker->RunWorkerAsync(text);
		}


		void OnSearchProgress(Object^ o, ProgressChangedEventArgs^ e)
		{
			bookmarks = gcnew List<BookMark^>((cli::array<BookMark^>^)e->UserState);
			ScrollBy((Vector)GetValue(OffsetProperty) + Vector(200, 0));
			UpdateBookmarks();		
		}



		property int PageIndex
		{
			int get() { return currentpage; }
		}

		
		property int PageCount
		{
			int get() { return document->PageCount(); }
		}

		
		property Rect RectImage
		{	
			Rect get() 
			{
				if (document == nullptr)
					return Rect::Empty;

				Rect rect = document->getPageBox(currentpage);
				return Rect(Point(0,0), rect.Size);
			}
		};

		void UpdateBookmarks()
		{									
			controlCanvas->Children->Clear();

			if (bookmarks == nullptr)
				return;

			int i = 0;

			Rect rectPage = RectImage;
			int frontpage = Math::Min(destPage, currentpage);
			int backpage = Math::Max(destPage, currentpage);

			double dblTransition		= (double)GetValue(TransitionProperty);			
			
			int animatedPageIndex = backpage + int(dblTransition *(frontpage - backpage));


			for each(BookMark^ bookmark in bookmarks)
			{
				TextBox^ box = gcnew TextBox();	
				box->Style = bookmarkStyle;

				Canvas::SetZIndex(box, -(int)(ComputePageOffset(bookmark->page, animatedPageIndex).X * 10000));
				box->Text = bookmark->matchcontext;
				box->DataContext = bookmark;
				box->ToolTip = bookmark->matchcontext + String::Format(", page {0}",bookmark->page + 1);

				box->MouseDown += gcnew MouseButtonEventHandler(this, &ImageControl::OnClickBookmark);

				double ypos = 5 + (i * 30) % ((int)rectPage.Bottom - 25);
				double offset = ComputePageOffset(bookmark->page, animatedPageIndex).X;
				Canvas::SetLeft(box, rectPage.Right + offset);
				Canvas::SetTop(box, ypos  + offset);

				if (bookmark->page == animatedPageIndex)
				{
					Canvas::SetLeft(box, rectPage.Right + offset - 20);				
					box->Padding = Thickness(23,3,3,3);
				}

				if (bookmark->exact)
				{
					box->Background = Brushes::LightGreen;
				}

				controlCanvas->Children->Add(box);					
				++i;
			}
		}		

		void OnClickBookmark(Object^ o, MouseButtonEventArgs^ e)
		{  
			e->Handled = true;
			FrameworkElement^ textblock = (FrameworkElement^)o;
			BookMark^ bookmark = (BookMark^)textblock->DataContext;
			MoveTo(bookmark->page, false);			
		}

		void Load(String^ fileName)
		{
			IntPtr str2;
			Monitor::Enter(docLock);

			try
			{
				str2 = Marshal::StringToHGlobalAnsi(fileName);
				document = PdfDocument::Create();
				document->Load((char*)(void*)str2);
				bitmapCache = gcnew cli::array<BitmapSource^>(document->PageCount()); 
			}
			finally
			{
				Monitor::Exit(docLock);
				Marshal::FreeHGlobal(str2);
			}
			InvalidateMeasure();
		}



		void OnLayoutUpdated(Object^ o, EventArgs^ e)
		{			
			if (document == nullptr)
				return;

			LayoutUpdated -= delOnLayoutUpdated;		
			CoerceValue(OffsetProperty);
			Notify("ZoomPanTransform");
			
			if (renderWorker == nullptr)
			{
				renderWorker = gcnew BackgroundWorker();

				renderWorker->DoWork += gcnew DoWorkEventHandler(this, &ImageControl::DoWork);
				renderWorker->RunWorkerCompleted += gcnew RunWorkerCompletedEventHandler(this, &ImageControl::OnCompleted);

				ScheduleUpdate(currentpage);						
				SetValue(ZoomProperty, Math::Log(document->ComputeDefaultZoom(0,ActualWidth, ActualHeight)));			
			}
		}

		virtual Windows::Size MeasureOverride(Windows::Size available) override
		{
			CoerceValue(OffsetProperty);
			Notify("ZoomPanTransform");
			return available;
		}

		
		property Transform^ ZoomPanTransform
		{
			Transform^ get()
			{
				if (document == nullptr)
					return gcnew TranslateTransform(0,0);

				TransformGroup^ group = gcnew TransformGroup();

				Point dispayCenter = Point(int(ActualWidth/2), int(ActualHeight/2));
				group->Children->Add(gcnew TranslateTransform(dispayCenter.X, dispayCenter.Y));

				Vector offsetFromCenter = (Vector)GetValue(OffsetProperty);				
				Size szImage = RectImage.Size;

				double dblZoom = ZoomFactor;

				Vector offset = dblZoom* (offsetFromCenter - Vector(szImage.Width / 2.0, szImage.Height / 2.0));

				group->Children->Add(gcnew TranslateTransform(int(offset.X)/dblZoom, int(offset.Y)/dblZoom));
				group->Children->Add(gcnew RotateTransform((double)GetValue(AngleProperty), dispayCenter.X, dispayCenter.Y));								
				group->Children->Add(gcnew ScaleTransform(dblZoom, dblZoom, dispayCenter.X, dispayCenter.Y));

				return group;
			}
		}


		property double StackThickNess
		{
			double get ()
			{			
				double pagecount = document->PageCount();
				return Math::Min(pagecount, sqrt(pagecount) * 2.0) * 2;
			}
		}

		static Geometry^ GeometryFromPoints(cli::array<Point>^ points)
		{
			PathFigure^ myPathFigure = gcnew PathFigure();
			myPathFigure->StartPoint = points[0];

			PathSegmentCollection^ myPathSegmentCollection = gcnew PathSegmentCollection();

			for (int i = 1; i < points->Length; ++ i)
			{
				LineSegment^ myLineSegment = gcnew LineSegment();
				myLineSegment->Point = points[i];
				myPathSegmentCollection->Add(myLineSegment);
			}

			myPathFigure->Segments = myPathSegmentCollection;

			PathFigureCollection^ myPathFigureCollection = gcnew PathFigureCollection();
			myPathFigureCollection->Add(myPathFigure);

			PathGeometry^ myPathGeometry = gcnew PathGeometry();
			myPathGeometry->Figures = myPathFigureCollection;
			return myPathGeometry;

		}



		Vector ComputePageOffset(int pageRequested, int current)
		{
			int pagecount = document->PageCount();

			double scale = ((pageRequested - current + pagecount) % pagecount) * 1.0  / pagecount;
			return Vector(StackThickNess * scale ,StackThickNess * scale);
		}


		void DrawStackSides(DrawingContext^ ctx, Rect rect, int animatedPageIndex)
		{
			// sides of the paper stack
			Rect shadowrect		= Rect::Offset(rect, Vector(StackThickNess,StackThickNess)); 
			Rect lastpageRect	= Rect::Offset(rect, ComputePageOffset(document->PageCount() - 1, animatedPageIndex)); 

			// right side of stack
			cli::array<Point>^ pointsRight = {rect.TopRight, shadowrect.TopRight,shadowrect.BottomRight, rect.BottomRight};
			ctx->DrawGeometry(Brushes::White, nullptr, GeometryFromPoints(pointsRight));			

			cli::array<Point>^ points1 = {shadowrect.TopRight, lastpageRect.TopRight,lastpageRect.BottomRight, shadowrect.BottomRight};
			ctx->DrawGeometry(Brushes::LightGray, nullptr, GeometryFromPoints(points1));			

			// bottom side of stack
			cli::array<Point>^ pointsBottom = {rect.BottomLeft, shadowrect.BottomLeft,shadowrect.BottomRight, rect.BottomRight};
			ctx->DrawGeometry(Brushes::LightGray, nullptr, GeometryFromPoints(pointsBottom));

			cli::array<Point>^ points2 = {lastpageRect.BottomLeft, shadowrect.BottomLeft,shadowrect.BottomRight, lastpageRect.BottomRight};
			ctx->DrawGeometry(Brushes::Gray, nullptr, GeometryFromPoints(points2));

		}

		void DrawLineForTargetPage(DrawingContext^ ctx, Rect rect, int animatedPageIndex)
		{
			if (targetPage == -1)
				return;

			Rect destpageRect	= Rect::Offset(rect, ComputePageOffset(targetPage, animatedPageIndex)); 
			cli::array<Point>^ points3 = {destpageRect.BottomLeft, destpageRect.BottomRight,destpageRect.TopRight};
			ctx->DrawGeometry(nullptr, gcnew Pen(Brushes::Black, 0.3), GeometryFromPoints(points3));
		}

		void DrawBackPage(DrawingContext^ ctx, Rect rect, int backpage)
		{
			if (bitmapCache[backpage] == nullptr)
			{
				ctx->DrawRectangle(Brushes::White, blackPen, rect);
				return;
			}

			double scale = 72.0/96.0;
			ctx->DrawImage(bitmapCache[backpage], Rect(0,0,bitmapCache[backpage]->Width * scale, bitmapCache[backpage]->Height * scale));
			ctx->DrawRectangle(Brushes::Transparent, blackPen, rect);
		}
	
		virtual void OnRender(DrawingContext^ ctx) override
		{				
			UserControl::OnRender(ctx);

			if (bitmapCache == nullptr)
				return;
			
			Rect rect = RectImage;

			int frontpage		  = Math::Min(destPage, currentpage);
			int backpage		  = Math::Max(destPage, currentpage);

			double dblTransition  = (double)GetValue(TransitionProperty);			
			int animatedPageIndex = backpage + int(dblTransition *(frontpage - backpage));

			DrawStackSides(ctx, rect, animatedPageIndex);	

			DrawLineForTargetPage(ctx, rect, animatedPageIndex);
			
			DrawBackPage(ctx, rect, backpage);

			DrawBookMarkHiglights(ctx, backpage);

			DrawCurrentSelection(ctx, backpage);

			if (dblTransition != 0) 
			{
				Rect rectFront = rect;
				rectFront.Width = rect.Width * dblTransition;
				BitmapSource^ srcFront = bitmapCache[frontpage];
				ctx->DrawImage(srcFront, rectFront);
				ctx->DrawRectangle(srcFront == nullptr ? Brushes::White : Brushes::Transparent, blackPen, rectFront);
			}			

		}




		bool GetSelectionRange(int% ifirst, int% ilast, List<Windows::Rect>^ rects)
		{			
			Windows::Rect rectSelect(dragStart, dragEnd);
			
			ifirst = -1;
			ilast  = -1;

			for (int i = 0; i < rects->Count; ++i)
			{
				if (!rects[i].IntersectsWith(rectSelect))
					continue;

				if (ifirst == -1)
				{
					ifirst = i;						
				}
				ilast  = i;
			}

			if (ifirst == -1)
				return false;

			return true;
		}



		void DrawCurrentSelection(DrawingContext^ ctx, int page)
		{
			if (double::IsNaN(dragStart.X))
				return;

			List<Windows::Rect>^ rects = gcnew List<Windows::Rect>();				
			String^ pagetext = document->GetPageText(page, rects);				

			int ifirst, ilast;
			if (!GetSelectionRange(ifirst, ilast, rects))
				return;

			currentSelection = pagetext->Substring(ifirst, ilast - ifirst + 1);
			if (!currentSelection->Contains(" "))
			{
				currentSelection = currentSelection->Replace("\r\n"," ");		
			}

			SolidColorBrush^ brush = gcnew SolidColorBrush(Colors::Black);
			brush->Opacity = 0.2;

			Rect rectLine;

			for (int i = ifirst; i <= ilast; ++i)
			{
				if (Rect::Union(rectLine, rects[i]).Height < rects[i].Height *1.05)
				{
					rectLine = Rect::Union(rectLine, rects[i]);
				}
				else
				{
					rectLine.Offset(1,2);
					ctx->DrawRectangle(brush, nullptr, rectLine);
					rectLine = rects[i];
				}				
			}		
			rectLine.Offset(1,2);
			ctx->DrawRectangle(brush, nullptr, rectLine);
		}

		void DrawBookMarkHiglights(DrawingContext^ ctx, int page)
		{
			using namespace System::Text::RegularExpressions;

			if (bookmarks == nullptr)
				return;

			for each (BookMark^ bookmark in bookmarks)
			{
				if (bookmark->page != page)
					continue;

				List<Windows::Rect>^ rects = gcnew List<Windows::Rect>();				
				String^ pagetext = document->GetPageText(page, rects);											
				MatchCollection^ matches = Regex::Matches(pagetext, bookmark->pattern, RegexOptions::IgnoreCase);				

				for each (Match^ match in matches)
				{
					Rect rect1 = rects[match->Groups[iWholeword]->Captures[0]->Index];
					Rect rect2 = rects[match->Groups[iWholeword]->Captures[0]->Index + match->Groups[iWholeword]->Captures[0]->Length - 1];
					Rect rect = Rect::Union(rect1, rect2);
					rect.Offset(1,3);
					rect.Inflate(2, 1);
					ctx->DrawRectangle(nullptr, gcnew Pen(Brushes::Orange, 2), rect);
				}					
			}
		}


		virtual void OnMouseLeave(MouseEventArgs^ ) override
		{
			SetToolTip(nullptr);
		}

		virtual void OnMouseMove(MouseEventArgs^ e) override
		{			
			if (e->LeftButton == MouseButtonState::Pressed && IsMouseCaptured)
			{
				dragEnd = e->GetPosition(this);
				InvalidateVisual();
			}

			if (e->OriginalSource != this)
			{
				SetToolTip(nullptr);
				return;
			}			

			UpdateFeedback(e->GetPosition(this));
		}

		virtual void OnRenderSizeChanged(SizeChangedInfo^) override
		{
			Notify("ZoomPanTransform");
		}

		virtual void OnLostMouseCapture(MouseEventArgs^) override
		{
		}


		virtual void OnMouseLeftButtonUp(MouseButtonEventArgs^) override
		{
			ReleaseMouseCapture();						
		}
		virtual void OnMouseLeftButtonDown(MouseButtonEventArgs^ e) override
		{
			Keyboard::Focus(this);

			Point clickPoint = e->GetPosition(this);		

			InvalidateVisual();
			dragEnd = Point(Double::NaN, Double::NaN);		
			if (CaptureMouse())
			{			
				dragStart = clickPoint;
			}				

			Point vec = clickPoint;

			// click on the stack's side
			int destpage = GetDestPage(vec);
			if (destpage != -1)
			{
				MoveTo(destpage, false);
				return;
			}

			// localize link
			int linkIndex = GetLinkIndex(vec);

			if (linkIndex == -1)
				return;

			Monitor::Enter(docLock);
			try
			{		
				Link link = Link(currentpage, linkIndex);
				String^ uristr = document->GetLinkUri(link);
				if (uristr->Length != 0) 
				{
					System::Uri^ uri = gcnew Uri(uristr, UriKind::Absolute);
					if (!uri->IsUnc && uri->Host->Length != 0)
					{
						System::Diagnostics::Process::Start(uri->ToString());
					}				
				}				
				else
				{
					int pagenumber = document->findPageNo(link);
					if (pagenumber != -1)
					{
						MoveBy(pagenumber - currentpage, true);
					}
				}
			}
			finally
			{
				Monitor::Exit(docLock);
			}
		}

		

		virtual void OnMouseDoubleClick(MouseButtonEventArgs^ e) override
		{
			Point vec = e->GetPosition(this);			
			Vector beginPos = (Vector) GetValue(OffsetProperty);
			ZoomBy(zoomed ? (1/1.4) : 1.4);
			zoomed = !zoomed;
		}


		

		void UpdateFeedback(Point pointMouse)
		{			
			Point vec = pointMouse;			

			if (GetDestPage(vec) != -1)
			{
				if (targetPage == GetDestPage(vec))
					return;

				targetPage = GetDestPage(vec);
				InvalidateVisual();
	
				return SetToolTip(String::Format("jump to page {0}", GetDestPage(vec) + 1));
			}
			targetPage = -1;

			int link = GetLinkIndex(vec);
			Cursor = link == -1 ? Cursors::Arrow : Cursors::Hand;
			String^ newTip = (link == -1) ? nullptr : GetLinkToolTip(link);	

			if (newTip != nullptr)
			{
				targetPage = GetLinkPageNumber(link);			
				return SetToolTip(newTip);
			}

			SetToolTip(nullptr);
		}

		void SetToolTip(String^ newTip)
		{
			if (newTip == nullptr)
			{
				tip->IsOpen = false;
				tip->Content = nullptr;			
				return;
			}

			if (newTip->Equals(tip->Content))
				return;

			// close tip to force it appear on a new location			
			tip->IsOpen = false;
			tip->Content = newTip;			
			tip->IsOpen = true;				
		}

		String^ GetLinkToolTip(int link)
		{
			if (!Monitor::TryEnter(docLock))
				return "waiting";

			try
			{	
				String^ uri = document->GetLinkUri(Link(currentpage, link));
				
				if (uri->Length != 0)
					return uri;					

				int pagenumber = document->findPageNo(Link(currentpage, link));
				return String::Format("page {0}", pagenumber + 1);				
			}
			finally
			{
				Monitor::Exit(docLock);
			}
		}

		int GetLinkPageNumber(int link)
		{
			if (!Monitor::TryEnter(docLock))
				return -1;

			try
			{										
				return document->findPageNo(Link(currentpage, link));
			}
			finally
			{
				Monitor::Exit(docLock);
			}
		}

		int GetDestPage(Point vec)
		{
			Rect rect = RectImage;
			if (vec.X > rect.Width)
			{
				double cpage =  document->PageCount() * (vec.X - rect.Width) / StackThickNess;
				if (cpage < document->PageCount())
				{
					return (currentpage + int(cpage)) % document->PageCount();
				}				
			}
			return -1;
		}

		virtual void OnQueryCursor(QueryCursorEventArgs^ e) override
		{	
			if (Keyboard::Modifiers == ModifierKeys::Control)
			{
				e->Cursor = Cursors::ScrollAll;
				return;
			}			
		}

		int GetLinkIndex(Point point)
		{
			if (!Monitor::TryEnter(docLock))
				return -1;

			try
			{				
				cli::array<Rect>^ rectLinks = document->GetLinks(currentpage);

				for(int i = 0; i < rectLinks->Length; ++i)
				{
					Rect rectLink = rectLinks[i];
					if (rectLink.Contains(point))
						return i;					
				}

				return -1;
			}
			finally
			{
				Monitor::Exit(docLock);
			}
		}


		

		static void AngleChanged(DependencyObject^ o,System::Windows::DependencyPropertyChangedEventArgs)
		{		
			ImageControl^ g = (ImageControl^)o;
			
			g->Notify("ZoomPanTransform");
		}

		static void TransitionChanged(DependencyObject^ o,System::Windows::DependencyPropertyChangedEventArgs)
		{		
			ImageControl^ g = (ImageControl^)o;
			g->UpdateBookmarks();
		}
		
		static void OffsetChanged(DependencyObject^ o,System::Windows::DependencyPropertyChangedEventArgs)
		{		
			ImageControl^ g = (ImageControl^)o;
			g->Notify("ZoomPanTransform");
		}

		static void ZoomChanged(DependencyObject^ o,System::Windows::DependencyPropertyChangedEventArgs)
		{		
			ImageControl^ g = (ImageControl^)o;

			g->CoerceValue(OffsetProperty);
			g->Notify("ZoomPanTransform");
		}

		static DependencyProperty^ ZoomProperty = DependencyProperty::Register("Zoom", Double::typeid, ImageControl::typeid,
			gcnew FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions::None, gcnew PropertyChangedCallback(&ImageControl::ZoomChanged)));

		static DependencyProperty^ TransitionProperty = DependencyProperty::Register("Transition", Double::typeid, ImageControl::typeid,
			gcnew FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions::AffectsRender, gcnew PropertyChangedCallback(&ImageControl::TransitionChanged)));

		static DependencyProperty^ AngleProperty = DependencyProperty::Register("Angle", Double::typeid, ImageControl::typeid,
			gcnew FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions::None, gcnew PropertyChangedCallback(&ImageControl::AngleChanged)));

		static DependencyProperty^ OffsetProperty = DependencyProperty::Register("Offset", Vector::typeid, ImageControl::typeid,
			gcnew FrameworkPropertyMetadata(Vector(0,0), 
			FrameworkPropertyMetadataOptions::None, 
			gcnew PropertyChangedCallback(&ImageControl::OffsetChanged),
			gcnew CoerceValueCallback(&ImageControl::CoerceOffset)
			));

		static Object^ CoerceOffset(DependencyObject^ d, Object^ value)
		{
			ImageControl^ g = (ImageControl^)d;
			return g->ClipVector((Vector)value);
		}

		ref class RenderData
		{
		public:
			double zoom;
			int page;
			bool prefetch;
			BitmapSource^ result;
		};


		bool ScheduleUpdate(int pageRequested)
		{
			RenderData^ data = gcnew RenderData();
			data->zoom = Math::Min(Math::Max(0.7,ZoomFactor), 4.0);			
			data->page = pageRequested;

			if (!UpdatePage(data))
			{
				updatepending = true;
				return false;
			}	

			return true;
		}


		bool UpdatePage(RenderData^ data)
		{	
			if (document == nullptr)
				return false;

			if (renderWorker->IsBusy)
				return false;

			renderWorker->RunWorkerAsync(data);
			return true;
		}


		void DoWork(Object^, DoWorkEventArgs^ e)
		{
			Monitor::Enter(docLock);
			try
			{
				RenderData^ data = (RenderData^)e->Argument;

				data->result = document->RenderPage(data->page, data->zoom);

				if (data->result == nullptr)
					return;
			
				e->Result = data;				

			}
			finally { Monitor::Exit(docLock); }
		}	


		void OnCompleted(Object^, RunWorkerCompletedEventArgs^ e)
		{			
			if (e->Result == nullptr)
			{
				if (DoPrefetching())
					return;
			}				

			RenderData^ data = (RenderData^)e->Result;
			bitmapCache[data->page] = data->result;

			if (destPage == currentpage && data->page == currentpage)
			{
				InvalidateVisual();				
			}

			if (bitmapCache[destPage] == nullptr)
			{
				if (DoPrefetching())
					return;				
			}

			if (updatepending)
			{
				updatepending = false;
				ScheduleUpdate(destPage);
				return;
			}
			DoPrefetching();

		}	

		bool DoPrefetching()
		{
			List<int>^ savepages = GetPrefetchPages(); 
			for each (int i in savepages)
			{
				if (i < 0 || i >= bitmapCache->Length)
					break;

				if (bitmapCache[i] == nullptr)
				{
					Prefetch(i);
					return true;
				}
			}
			return false;
		}


		void Prefetch(int pagePrefetch)
		{			
			if (!(pagePrefetch < bitmapCache->Length))
				return;

			if (bitmapCache[pagePrefetch] != nullptr)
				return;

			RenderData^ data = gcnew RenderData();
			data->page = pagePrefetch;
			data->prefetch = true;
			data->zoom = 0.7;

			UpdatePage(data);
		}


		virtual event PropertyChangedEventHandler^ PropertyChanged;

	public:
		int ClipPageNumber(int pageNumber)
		{
			if (pageNumber < 0)
				return 0;

			if (pageNumber >= document->PageCount())
				return document->PageCount() - 1;

			return pageNumber;
		}

		property double ZoomFactor
		{
			double get() { return Math::Exp((double)GetValue(ZoomProperty)); }
		}

		void MoveBy(int i, bool fast)
		{		
			MoveTo(ClipPageNumber(destPage + i), fast);
		}

		void StopZoom()
		{	
			StopAnimation(ZoomProperty);
			zoomanimation = nullptr;
			ScheduleUpdate(currentpage);
		}

		void StartZoom(double factor)
		{
			if (zoomanimation != nullptr)
				return;

			zoomanimation = gcnew DoubleAnimation();
			zoomanimation->Duration = TimeSpan::FromMilliseconds(1000);
			zoomanimation->By =  Math::Log(factor);			
			zoomanimation->RepeatBehavior = RepeatBehavior::Forever;
			zoomanimation->IsCumulative = true;

			zoomanimation->Completed += gcnew EventHandler(this, &ImageControl::OnZoomCompleted);		

			BeginAnimation(ZoomProperty, zoomanimation, HandoffBehavior::Compose);								
		}


		void StopScroll()
		{	
			StopAnimation(OffsetProperty);
			offsetanimation = nullptr;
		}

		void StartScroll(Vector speed)
		{
			if (offsetanimation != nullptr)
				return;

			offsetanimation = gcnew VectorAnimation();
			offsetanimation->Duration = TimeSpan::FromMilliseconds(20 * 1000);
			offsetanimation->By = - 20 * speed / ZoomFactor;			
			offsetanimation->AccelerationRatio = 0.01;

			BeginAnimation(OffsetProperty, offsetanimation, HandoffBehavior::Compose);			
		}

		void ZoomTo(double factor)
		{		
			zoomanimation = gcnew DoubleAnimation();
			zoomanimation->Duration = TimeSpan::FromMilliseconds(ticks);
			zoomanimation->To = Math::Log(factor);			
			zoomanimation->AccelerationRatio = 0.2;
			zoomanimation->Completed += gcnew EventHandler(this, &ImageControl::OnZoomCompleted);			

			BeginAnimation(ZoomProperty, zoomanimation, HandoffBehavior::Compose);								
		}

		void ZoomBy(double factor)
		{		
			zoomanimation = gcnew DoubleAnimation();
			zoomanimation->Duration = TimeSpan::FromMilliseconds(ticks);
			zoomanimation->By = Math::Log(factor);			
			zoomanimation->AccelerationRatio = 0.2;
			zoomanimation->Completed += gcnew EventHandler(this, &ImageControl::OnZoomCompleted);			

			BeginAnimation(ZoomProperty, zoomanimation, HandoffBehavior::Compose);								
		}

		property double ScrollHeight 
		{
			double get() 
			{ 
				
				return Math::Abs((ClipVector(Vector(1e6, 1e6)) - ClipVector( Vector(-1e6, -1e6))).Y); 
			}
		}

		property bool CanShowFullPage
		{
			bool get() { return ScrollHeight < ActualHeight / 20;}
		}

		property double AdvisedScrollHeight 
		{
			double get() 
			{ 
				double screenCount = Math::Max(1.0,Math::Round(ScrollHeight * ZoomFactor / (ActualHeight*2/3)));				
				return ScrollHeight * 1.01 / screenCount;
			}
		}


		bool ScrollBy(Vector delta)
		{
			Vector beginPos = (Vector) GetValue(OffsetProperty);

			Vector endPos = ClipVector(beginPos - delta);

			if (beginPos == endPos)
				return (Vector) GetValue(OffsetProperty) != endPos;

			VectorAnimation^ offsetanimation = gcnew VectorAnimation(endPos, TimeSpan::FromMilliseconds(ticks * 1.5));
			offsetanimation->AccelerationRatio = 0.2;
			offsetanimation->DecelerationRatio = 0.4; 
			BeginAnimation(OffsetProperty, offsetanimation, HandoffBehavior::Compose);			

			return true;
		}

		void RotateBy(double angle)
		{
			DoubleAnimation^ angleanimation = gcnew DoubleAnimation();
			angleanimation->Duration = TimeSpan::FromMilliseconds(ticks);
			angleanimation->By = angle;
			angleanimation->AccelerationRatio = 0.2;
			angleanimation->DecelerationRatio = 0.4;
			BeginAnimation(AngleProperty, angleanimation, HandoffBehavior::Compose);						
		}


		void Back()
		{
			if (history->Count == 0)
				return;

			int dest = history[history->Count-1];
			history->RemoveAt(history->Count-1);
			MoveToCore(dest, false);
		}

		void MoveTo(int dest, bool fast)
		{
			if (document == nullptr)
				return;

			if (dest == currentpage)
				return;

			if (dest < 0)
				return;

			history->Add(currentpage);

			MoveToCore(dest, fast);			
		}


	protected:
		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		~ImageControl()
		{			
			delete document;			
		}


	private:
		List<int>^ GetPrefetchPages()
		{
			List<int>^ savepages = gcnew List<int>();
			savepages->Add(destPage);
			savepages->Add(destPage + 1);
			savepages->Add(destPage + 2);
			savepages->Add(currentpage);
			savepages->Add(currentpage + 1);
			savepages->Add(currentpage - 1);
			savepages->Add(0);
			return savepages;
		}

		void MoveToCore(int dest, bool fast)
		{
			OnFrontTransitionCompleted(nullptr, nullptr);

			destPage = dest;

			List<int>^ savepages = GetPrefetchPages(); 
			for (int i = 0; i < bitmapCache->Length; ++i)
			{			
				if (!savepages->Contains(i))
				{
					bitmapCache[i] = nullptr;
				}
			}

			SetToolTip(nullptr);
			InvalidateVisual();
			dragEnd = Point(Double::NaN, Double::NaN);		
			currentSelection = nullptr;

			ScheduleUpdate(destPage);

			if (bitmapCache[destPage] == nullptr)
			{
				fast = false;
			}

			DoTransition(fast);
			UpdateBookmarks();
		}

		void DoTransition(bool fast)
		{
			int ms = fast ? 150 : 300;

			DoubleAnimation^ transitionanimation;

			if (destPage < currentpage)
			{
				transitionanimation = gcnew DoubleAnimation(0.0, 1.0, Duration(TimeSpan::FromMilliseconds(ms)));				
				transitionanimation->DecelerationRatio = 0.25;
				transitionanimation->Completed += gcnew EventHandler(this, &ImageControl::OnBackTransitionCompleted);							
			}
			else
			{
				transitionanimation = gcnew DoubleAnimation(1.0, 0.0, Duration(TimeSpan::FromMilliseconds(ms)));
				transitionanimation->AccelerationRatio = 0.10;
				transitionanimation->Completed += gcnew EventHandler(this, &ImageControl::OnFrontTransitionCompleted);							
			}

			BeginAnimation(TransitionProperty, transitionanimation);								
		}


		static const int ticks = 500;						

		Vector ClipVector(Vector vec)
		{		
			Size size = RectImage.Size;
			double angle = abs(fmod((double)GetValue(AngleProperty), 180));

			double width = ActualWidth/(2 * ZoomFactor);
			double height = ActualHeight/(2 * ZoomFactor);

			if (45 < angle && angle < 135)
			{
				double temp = width;
				width = height;
				height = temp;				
			}

			double maxX = max(0,10 + (size.Width + 150)/2  - width);
			double maxY = max(0,10 + size.Height/2  - height);
			vec.X = min(max(-maxX - 150, vec.X), maxX);
			vec.Y = min(max(-maxY, vec.Y), maxY);
			return vec;
		}

		void OnZoomCompleted(Object^ o, EventArgs^ )
		{					
			AnimationClock^ clock = (AnimationClock^)o;

			if (zoomanimation == nullptr || clock->Timeline->BeginTime.Equals(zoomanimation->BeginTime))
			{
				ScheduleUpdate(currentpage);
			}
			zoomanimation = nullptr;
		}

		void OnFrontTransitionCompleted(Object^, EventArgs^)
		{			
			currentpage = destPage;
			BeginAnimation(TransitionProperty, nullptr);							
			SetValue(TransitionProperty, 0.0);
			targetPage = -1;
			InvalidateVisual();
			UpdateFeedback(Mouse::GetPosition(this));
		}

		void OnBackTransitionCompleted(Object^, EventArgs^)
		{			
			currentpage = destPage;
			BeginAnimation(TransitionProperty, nullptr);					
			SetValue(TransitionProperty, 0.0);
			targetPage = -1;
			InvalidateVisual();
			UpdateFeedback(Mouse::GetPosition(this));
		}

		void Notify(String^ propertyName)
		{			
			PropertyChanged(this, gcnew PropertyChangedEventArgs(propertyName));

		}

	};





	void SearchWorker::OnDoWork(DoWorkEventArgs^ e)
	{			
		String^ searchText = (String^)e->Argument;
		String^ pattern = Pattern(searchText);

		List<BookMark^>^ results = gcnew List<BookMark^>();
		PdfDocument* document =  mycontrol->GetDocument(); 
		for (int ipage = 0; ipage < document->PageCount(); ++ipage)
		{		
			Thread::Sleep(0);

			if (CancellationPending)
				return;

			Monitor::Enter(mycontrol->GetDocLock());
			try
			{				
				if (CancellationPending)
					return;

				if (ipage % 16 == 8)
				{
					ReportProgress(0, results->ToArray());
				}

				MatchCollection^ matches = Regex::Matches(document->GetPageText(ipage, nullptr), pattern, RegexOptions::IgnoreCase);

				if (matches->Count == 0)
					continue;

				results->Add(gcnew BookMark(ipage, GetBookmarkText(matches, searchText), pattern, IsExact(matches, searchText)));						
			}
			finally { Monitor::Exit(mycontrol->GetDocLock()); }

		}

		ReportProgress(0, results->ToArray());
		e->Result = results;				
	}	

}